package fr.dauphine.bookstore.modele;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.MapKeyManyToMany;

/**
 * Commande.
 */
@Entity
@Table(name = "BookstoreOrder")
public final class Order extends Persistent {

	/**
	 * Serial version UID.
	 */
	private static final long serialVersionUID = -3014172845839149058L;

	/**
	 * Statut 'Non reglee'.
	 */
	public static final int STATUS_DRAFT = 0;

	/**
	 * Statut 'Reglee'.
	 */
	public static final int STATUS_ORDERED = 1;

	/**
	 * Statut 'Envoyee'.
	 */
	public static final int STATUS_SHIPPED = 2;

	/**
	 * Utilisateur.
	 */
	private User user;

	/**
	 * Livres et leurs quantites.
	 */
	private Map<Book, Integer> books = new HashMap<Book, Integer>();

	/**
	 * Date de reglement.
	 */
	private Date orderDate;

	/**
	 * Date d'envoi.
	 */
	private Date shippingDate;

	// Methodes Get&Set

	/**
	 * Retourne la date de reglement.
	 * 
	 * @return Date de reglement
	 */
	public Date getOrderDate() {
		return orderDate;
	}

	/**
	 * Met a jour la date de reglement.
	 * 
	 * @param orderDate
	 *            Date de reglement
	 */
	public void setOrderDate(final Date orderDate) {
		this.orderDate = orderDate;
	}

	/**
	 * Retourne la date d'envoi.
	 * 
	 * @return Date d'envoi
	 */
	public Date getShippingDate() {
		return shippingDate;
	}

	/**
	 * Met a jour la date d'envoi.
	 * 
	 * @param shippingDate
	 *            Date d'envoi
	 */
	public void setShippingDate(final Date shippingDate) {
		this.shippingDate = shippingDate;
	}

	/**
	 * Retourne l'utilisateur.
	 * 
	 * @return Utilisateur
	 */
	@ManyToOne
	public User getUser() {
		return user;
	}

	/**
	 * Met a jour l'utilisateur.
	 * 
	 * @param user
	 *            Utilisateur
	 */
	public void setUser(final User user) {
		this.user = user;
	}

	/**
	 * Retourne les livres et leurs quantites.
	 * 
	 * @return Livres et leurs quantites
	 */
	@JoinTable(name = "OrderLine")
	@CollectionOfElements
	@MapKeyManyToMany(targetEntity = Book.class, joinColumns = {@JoinColumn(name = "Book_id") })
	@Column(name = "quantity")
	public Map<Book, Integer> getBooks() {
		return books;
	}

	/**
	 * Met a jour les livres et leurs quantites.
	 * 
	 * @param books
	 *            Livres et leurs quantites
	 */
	public void setBooks(final Map<Book, Integer> books) {
		this.books = books;
	}

	// Methodes de calcul

	/**
	 * Calcule le statut de la commande.
	 * <ul>
	 * <li>Date d'envoi => Envoyee</li>
	 * <li>Date d'achat => Reglee</li>
	 * <li>Autres cas => Non reglee</li>
	 * </ul>
	 * 
	 * @return Statut de la commande
	 */
	public int calculateStatus() {
		if (getShippingDate() != null) {
			return STATUS_SHIPPED;

		} else if (getOrderDate() != null) {
			return STATUS_ORDERED;

		} else {
			return STATUS_DRAFT;
		}
	}

	/**
	 * Calcule le sous-total pour un livre donne.
	 * 
	 * @param book
	 *            Livre
	 * @return Sous-total pour book.
	 */
	public BigDecimal calculateSubTotal(final Book book) {
		Integer quantity = getBooks().get(book);

		return book.getPrice().multiply(new BigDecimal(quantity));
	}

	/**
	 * Calcule le total de la commande.
	 * 
	 * @return Total
	 */
	public BigDecimal calculateTotal() {
		BigDecimal total = BigDecimal.ZERO;

		for (Entry<Book, Integer> entry : getBooks().entrySet()) {
			Book book = entry.getKey();
			total = total.add(calculateSubTotal(book));
		}

		return total;
	}

	// Surcharges

	/**
	 * @return Le numero
	 */
	public String toString() {
		return getId().toString();
	}	
	
	/**
	 * Teste l'egalite avec un objet quelconque.
	 * 
	 * @param that
	 *            Objet quelconque
	 * @return Vrai si l'objet that est egal a l'objet courant, faux sinon
	 */
	public boolean equals(final Object that) {
		return (that != null && that instanceof Order && equals((Order) that));
	}
}
